// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//


#import "WWSceneClock.h"
#import "WW3DWell.h"  // for delegate routine
#import "WWEveParser.h"  // for setTicksPerSecond

@implementation WWSceneClock

+ initialize { return [WWSceneClock setVersion:2], self; }

- init
{
  [super init];

  currentTime = 0.0;
  samplesPerSecond = 60.0;
  increment = 1./samplesPerSecond;
  skip = 10. * increment;
  timedEntry = (DPSTimedEntry)0;
  ratio = 1.;
  isPaused = YES;
  mark = 3.0;

  return self;
}

- awake
{
  [super awake];
  timedEntry = (DPSTimedEntry)0;
  isPaused = YES;
  return self;
}

- (BOOL)isPaused { return isPaused; }

- removeTimedEntry
{
  if (timedEntry)
  {  DPSRemoveTimedEntry(timedEntry);
     timedEntry = (DPSTimedEntry)0;
     isPaused = YES;
  }
  isPaused = YES;
  return self;
}

- free
{
   [self removeTimedEntry];
   return [super free];
}

- reset:sender
{
   [self removeTimedEntry];
   currentTime = 0.0;
   [delegate revertSceneClockInspector];
   return self;
}

- (float)mark { return mark; }

- setMark:(float)newMark 
{  mark = newMark; 
   [delegate revertSceneClockInspector];
   return self; 
}

- takeMark:sender
{
   [self setMark:[sender floatValue]];
   return self;
}

/////////////////////////////////////////////////////////////////////////
///////////////////////// increment /////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
- (float)increment {  return increment; }

- increment:sender
{
   [self removeTimedEntry];
   currentTime += increment;
   [delegate revertSceneClockInspector];
   return self;
}

- setIncrement:(float)newIncrement
{
   if (newIncrement <= 0.0)
   {  NXLogError("sceneClockIncrement must be > 0.\n");
      return nil;
   }
   increment = newIncrement;
   // need to tell the model's interp about the value of samplesPerSecond
   // set the increment appropriately...
   samplesPerSecond = 1.0/increment;
   [scene setTicksPerSecond:samplesPerSecond];
   [delegate revertSceneClockInspector];
   return self;
}

/////////////////////////////////////////////////////////////////////////
///////////////////////// skip /////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
- (float)skip {  return skip; }

- skip:sender
{
   [self removeTimedEntry];
   currentTime += skip;
   return self;
}

- setSkip:(float)newSkip
{
   if (newSkip <= 0.0)
   {  NXLogError("sceneClockSkip must be > 0.\n");
      return nil;
   }
   skip = newSkip;
   // need to tell the model's interp about the value of samplesPerSecond
   // set the skip appropriately...
   [delegate revertSceneClockInspector];
   return self;
}

/////////////////////////////////////////////////////////////////////////
///////////////////////// current time //////////////////////////////////
/////////////////////////////////////////////////////////////////////////
- (float)timestamp { return currentTime; }
- setCurrentTime:(float)newTime
{
   if (newTime < 0.0)
   {  NXLogError("negative time is not valid in WavesWorld.\n");
      return nil;
   }
   [self removeTimedEntry];
   currentTime = newTime;
   [delegate revertSceneClockInspector];
   return self;
}


- decrement:sender
{
   [self removeTimedEntry];
   currentTime -= increment;
   if (currentTime < 0.0)
   {  currentTime = 0.0;
   }
   [delegate revertSceneClockInspector];
   return self;
}

static void fastForwardFunc(DPSTimedEntry tag, double now, char *userdata)
{
   WWSceneClock  *self = (id)userdata;

   self->currentTime += self->skip;
   if (self->currentTime > self->mark)
   {  // I'm at the stop mark; so stop
      [self->delegate pause:nil];
   }
   // should probably send a msg to a delegate to tell it that the scene info has changed...
   [self->delegate revertSceneClockInspector];

   return ;
}

- fastForward:sender
{
   [self removeTimedEntry];
   timedEntry = DPSAddTimedEntry((double)(increment * ratio),
				 (DPSTimedEntryProc)fastForwardFunc,
				 self,
				 NX_MODALRESPTHRESHOLD);
   isPaused = NO;
   return self;
}

static void rewindFunc(DPSTimedEntry tag, double now, char *userdata)
{
   WWSceneClock  *self = (id)userdata;

   self->currentTime -= self->skip;
   if (self->currentTime < 0.0)
   {  self->currentTime = 0.0;
      // I'm totally rewound, stop 
      [self->delegate pause:nil];
   }
   // should probably send a msg to a delegate to tell it that the scene info has changed...
   [self->delegate revertSceneClockInspector];

   return ;
}

- rewind:sender
{
   [self removeTimedEntry];
   timedEntry = DPSAddTimedEntry((double)(increment * ratio),
				 (DPSTimedEntryProc)rewindFunc,
				 self,
				 NX_MODALRESPTHRESHOLD);
   isPaused = NO;
   return self;
}

- pause:sender
{
   [self removeTimedEntry];
   [delegate revertSceneClockInspector];
   return self;
}

static void playFunc(DPSTimedEntry tag, double now, char *userdata)
{
   WWSceneClock  *self = (id)userdata;

   if (!self->waiting)  // great! we can increment!
   {  self->currentTime += self->increment;
   }
   else
   {  if (

      }
   if (self->currentTime > self->mark)
   {  // I'm at the stop mark; so stop
      [self->delegate pause:nil];
   }
   else
   {  // restart stuff
      [self installHighFrequencyCheck];  // back to initial mode
   }

   // should probably send a msg to a delegate to tell it that the scene info has changed...
   [self->delegate revertSceneClockInspector];

   return ;
}

- installLowFrequencyCheck
{
   [self removeTimedEntry];
   timedEntry = DPSAddTimedEntry((double)(lowFrequency * increment * ratio),
				 (DPSTimedEntryProc)playFunc,
				 self,
				 NX_MODALRESPTHRESHOLD);
   startTime = ; // get current wall time
   dropDeadTime = startTime + (increment * ratio);
   count = 0;
   waiting = YES;
   isPaused = NO;
   return self;
}

- installHighFrequencyCheck
{
   [self removeTimedEntry];
   timedEntry = DPSAddTimedEntry((double)(highFrequency * increment * ratio),
				 (DPSTimedEntryProc)playFunc,
				 self,
				 NX_MODALRESPTHRESHOLD);
   count = 0;
   waiting = YES;
   isPaused = NO;
   return self;
}

- play:sender
{
   return [self installHighFrequencyCheck];
}

/////////////////////////////////////////////////////////////////////////
///////////////////////// samples/sec //////////////////////////////////
/////////////////////////////////////////////////////////////////////////
- (float)samplesPerSecond { return samplesPerSecond; }

- setSamplesPerSecond:(float)newRate 
{ 
   if (newRate < 0.0)
   {  NXLogError("scene sample rate must be positive, not %d\n", newRate);
      return nil;
   }
   [self removeTimedEntry];
   samplesPerSecond = newRate; 
   increment = 1.0/samplesPerSecond; 
   skip = 10. * increment;
   [scene setTicksPerSecond:samplesPerSecond];
   [delegate revertSceneClockInspector];
   return self; 
}

- (float)ratio { return ratio; }

- setRatio:(float)newRatio 
{ 
   if (newRatio < 0.0)
   {  NXLogError("the ratio of real time to scene time must be positive, not %d\n", newRatio);
      return nil;
   }
   [self removeTimedEntry];
   ratio = newRatio; 
   [delegate revertSceneClockInspector];
   return self; 
}

- setDelegate:newDelegate { delegate = newDelegate; return self; }
- setScene:newScene { scene = newScene; return self; }

// NeXTSTEP archiving:

#define typeVectorVersion1 "fffff"
#define typeValuesVersion1 &currentTime, &increment, &skip, &samplesPerSecond, &ratio

#define typeVector "ffffff"
#define typeValues &currentTime, &increment, &skip, &samplesPerSecond, &ratio, &mark

- read:(NXTypedStream*)stream 
{
  int version;


  [super read:stream];

  version = NXTypedStreamClassVersion(stream,"WWSceneClock");
  if (version == 0) NXReadTypes(stream,"i",&version), version=1;
  if (version == 1)
  {  NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
     scene = NXReadObject(stream);
     delegate = NXReadObject(stream);
     mark = 3.0;
  }
  if (version == 2)
  {  NXReadTypes(stream, typeVector, typeValues);
     scene = NXReadObject(stream);
     delegate = NXReadObject(stream);
  }

  return self; 
}

- write:(NXTypedStream*)stream 
{
  [super write:stream];

  NXWriteTypes(stream,typeVector, typeValues);
  NXWriteObjectReference(stream, scene);
  NXWriteObjectReference(stream, delegate);

  return self;
}


@end
